<template>
    <a-control-group :direction="'v'" :block="true" class="a-input-rich-text">
        <a-control-group
            class="toolbar c-f-shrink-0"
            :class="[
                `c-bg-color-${formItemHook.myColor}`,
                !formItemHook.mySharp ? `c-round-${formItemHook.mySize}` : '',
            ]"
        >
            <a-dropdown-button-group :displayCount="0" :trigger="'hover'" ref="headerDropdownRef">
                <a-button
                    :color="formItemHook.myColor"
                    :size="formItemHook.mySize"
                    :disabled="formItemHook.myDisabled"
                    :sharp="i == 1 ? true : formItemHook.mySharp"
                    :square="true"
                    :active="state.currentFormat.header == i"
                    @click="methods.formatHeader(i)"
                    v-for="i in 6"
                    :key="i"
                >
                    H{{ i }}
                </a-button>

                <template v-slot:moreBtn>
                    <a-button
                        :color="formItemHook.myColor"
                        :size="formItemHook.mySize"
                        :disabled="formItemHook.myDisabled"
                        :sharp="true"
                        :square="true"
                        :active="!!state.currentFormat.header"
                        class="with-angle"
                        @click="methods.formatHeader(false)"
                    >
                        <span class="fa fa-fw fa-header"></span>
                        <div class="angle" :class="[`c-bg-color-${formItemHook.myColor}-reverse`]"></div>
                    </a-button>
                </template>
            </a-dropdown-button-group>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                :active="!!state.currentFormat.bold"
                @click="methods.format('bold')"
            >
                <span class="fa fa-fw fa-bold"></span>
            </a-button>
            <a-dropdown :trigger="'hover'" ref="sizeDropdownRef">
                <a-button
                    :color="formItemHook.myColor"
                    :size="formItemHook.mySize"
                    :disabled="formItemHook.myDisabled"
                    :sharp="formItemHook.mySharp"
                    :square="true"
                    :active="!!state.currentFormat.size"
                    class="with-angle"
                    @click="methods.formatSize(false)"
                >
                    <span class="fa fa-fw fa-text-height"></span>
                    <div class="angle" :class="[`c-bg-color-${formItemHook.myColor}-reverse`]"></div>
                </a-button>

                <template v-slot:pop>
                    <a-control-group :direction="'v'">
                        <a-button
                            :color="formItemHook.myColor"
                            :size="formItemHook.mySize"
                            :disabled="formItemHook.myDisabled"
                            :sharp="i == 0 ? true : formItemHook.mySharp"
                            :active="state.currentFormat.size == fontSize.value"
                            @click="methods.formatSize(fontSize.value)"
                            v-for="(fontSize, i) in [
                                {
                                    text: $t('AInputRichText.Small'),
                                    value: 'small',
                                },
                                {
                                    text: $t('AInputRichText.Large'),
                                    value: 'large',
                                },
                                {
                                    text: $t('AInputRichText.Huge'),
                                    value: 'huge',
                                },
                            ]"
                            :key="fontSize.text"
                        >
                            {{ fontSize.text }}
                        </a-button>
                    </a-control-group>
                </template>
            </a-dropdown>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                :active="!!state.currentFormat.italic"
                @click="methods.format('italic')"
            >
                <span class="fa fa-fw fa-italic"></span>
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                :active="!!state.currentFormat.underline"
                @click="methods.format('underline')"
            >
                <span class="fa fa-fw fa-underline"></span>
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                :active="!!state.currentFormat.strike"
                @click="methods.format('strike')"
            >
                <span class="fa fa-fw fa-strikethrough"></span>
            </a-button>
            <a-dropdown :trigger="'hover'" ref="colorDropdownRef">
                <a-button
                    :color="formItemHook.myColor"
                    :size="formItemHook.mySize"
                    :disabled="formItemHook.myDisabled"
                    :sharp="formItemHook.mySharp"
                    :square="true"
                    :active="!!state.currentFormat.color"
                    class="with-angle"
                    @click="methods.formatColor(false)"
                >
                    <span>C</span>
                    <div class="angle" :class="[`c-bg-color-${formItemHook.myColor}-reverse`]"></div>
                </a-button>
                <template v-slot:pop>
                    <div class="color-container" :class="[`c-round-${formItemHook.mySize}`]">
                        <div
                            v-for="(color, i) in [
                                '#000000',
                                '#e60000',
                                '#ff9900',
                                '#ffff00',
                                '#008a00',
                                '#0066cc',
                                '#9933ff',
                                '#ffffff',
                                '#facccc',
                                '#ffebcc',
                                '#ffffcc',
                                '#cce8cc',
                                '#cce0f5',
                                '#ebd6ff',
                                '#bbbbbb',
                                '#f06666',
                                '#ffc266',
                                '#ffff66',
                                '#66b966',
                                '#66a3e0',
                                '#c285ff',
                                '#888888',
                                '#a10000',
                                '#b26b00',
                                '#b2b200',
                                '#006100',
                                '#0047b2',
                                '#6b24b2',
                                '#444444',
                                '#5c0000',
                                '#663d00',
                                '#666600',
                                '#003700',
                                '#002966',
                                '#3d1466',
                            ]"
                            :key="color"
                            class="color-item"
                            :class="[`c-square-${formItemHook.mySize}`, state.currentFormat.color == color ? 'on' : '']"
                            :style="{ background: color }"
                            @click="methods.formatColor(color)"
                        ></div>
                    </div>
                </template>
            </a-dropdown>
            <a-dropdown :trigger="'hover'" ref="backgroundDropdownRef">
                <a-button
                    :color="formItemHook.myColor"
                    :size="formItemHook.mySize"
                    :disabled="formItemHook.myDisabled"
                    :sharp="formItemHook.mySharp"
                    :square="true"
                    :active="!!state.currentFormat.background"
                    class="with-angle"
                    @click="methods.formatBackground(false)"
                >
                    <span>Bg</span>
                    <div class="angle" :class="[`c-bg-color-${formItemHook.myColor}-reverse`]"></div>
                </a-button>
                <template v-slot:pop>
                    <div class="color-container" :class="[`c-round-${formItemHook.mySize}`]">
                        <div
                            v-for="(background, i) in [
                                '#000000',
                                '#e60000',
                                '#ff9900',
                                '#ffff00',
                                '#008a00',
                                '#0066cc',
                                '#9933ff',
                                '#ffffff',
                                '#facccc',
                                '#ffebcc',
                                '#ffffcc',
                                '#cce8cc',
                                '#cce0f5',
                                '#ebd6ff',
                                '#bbbbbb',
                                '#f06666',
                                '#ffc266',
                                '#ffff66',
                                '#66b966',
                                '#66a3e0',
                                '#c285ff',
                                '#888888',
                                '#a10000',
                                '#b26b00',
                                '#b2b200',
                                '#006100',
                                '#0047b2',
                                '#6b24b2',
                                '#444444',
                                '#5c0000',
                                '#663d00',
                                '#666600',
                                '#003700',
                                '#002966',
                                '#3d1466',
                            ]"
                            :key="background"
                            class="color-item"
                            :class="[
                                `c-square-${formItemHook.mySize}`,
                                state.currentFormat.background == background ? 'on' : '',
                            ]"
                            :style="{ background: background }"
                            @click="methods.formatBackground(background)"
                        ></div>
                    </div>
                </template>
            </a-dropdown>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                @click="methods.openImageSelector"
            >
                <span class="fa fa-fw fa-image"></span>
                <input
                    type="file"
                    ref="imageInputRef"
                    @change="methods.inputImage"
                    class="c-hide"
                    accept="image/jpeg, image/bmp, image/png, image/gif"
                />
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                :active="!!state.currentFormat.link"
                @click="methods.showLinkInput"
            >
                <span class="fa fa-fw fa-chain"></span>
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                @click="methods.cancelLink"
            >
                <span class="fa fa-fw fa-chain-broken"></span>
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                :active="state.currentFormat.list == 'ordered'"
                @click="methods.formatList('ordered')"
            >
                <span class="fa fa-fw fa-list-ol"></span>
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                :active="state.currentFormat.list == 'bullet'"
                @click="methods.formatList('bullet')"
            >
                <span class="fa fa-fw fa-list-ul"></span>
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                @click="methods.formatIndent('+1')"
            >
                <span class="fa fa-fw fa-indent"></span>
            </a-button>
            <a-button
                :color="formItemHook.myColor"
                :size="formItemHook.mySize"
                :disabled="formItemHook.myDisabled"
                :sharp="formItemHook.mySharp"
                :square="true"
                @click="methods.formatIndent('-1')"
            >
                <span class="fa fa-fw fa-outdent"></span>
            </a-button>
            <a-dropdown-button-group :displayCount="0" :trigger="'hover'" ref="alignDropdownRef">
                <a-button
                    :color="formItemHook.myColor"
                    :size="formItemHook.mySize"
                    :disabled="formItemHook.myDisabled"
                    :sharp="i == 0 ? true : formItemHook.mySharp"
                    :square="true"
                    :active="state.currentFormat.align == align"
                    @click="methods.formatAlign(align)"
                    v-for="(align, i) in ['center', 'right']"
                    :key="align"
                >
                    <span class="fa fa-fw" :class="[`fa-align-${align}`]"></span>
                </a-button>

                <template v-slot:moreBtn>
                    <a-button
                        :color="formItemHook.myColor"
                        :size="formItemHook.mySize"
                        :disabled="formItemHook.myDisabled"
                        :sharp="true"
                        :square="true"
                        :active="!!state.currentFormat.align"
                        class="with-angle"
                        @click="methods.formatAlign(false)"
                    >
                        <span class="fa fa-fw fa-align-justify"></span>
                        <div class="angle" :class="[`c-bg-color-${formItemHook.myColor}-reverse`]"></div>
                    </a-button>
                </template>
            </a-dropdown-button-group>
        </a-control-group>
        <div
            class="editor c-b-t-0"
            :class="[
                `color-${formItemHook.myColor}`,
                `size-${formItemHook.mySize}`,
                { sharp: formItemHook.mySharp },
                { disabled: formItemHook.myDisabled },
            ]"
            ref="editorRef"
        ></div>
    </a-control-group>
</template>

<style lang="scss" scoped>
@import '../scss/vars';

.a-input-rich-text {
    .editor {
        min-height: 400px;
        box-sizing: border-box;
        outline: none;
        transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;

        &.disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        @each $colorPackageName, $colorPackageValue in $colorPackages {
            $bgColor: map-get($colorPackageValue, 'bgColor');

            &.color-#{$colorPackageName} {
                background-color: $defaultBgColor;
                border: solid 1px $bgColor;

                &:focus:not(.disabled) {
                    border-color: darken($bgColor, 20%);
                    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px lighten($bgColor, 10%);
                }
            }
        }

        @each $sizePackageName, $sizePackageValue in $sizePackages {
            &.size-#{$sizePackageName} {
                &:not(.sharp) {
                    border-radius: map-get($sizePackageValue, 'borderRadius');
                }
            }
        }
    }
}

.color-container {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    grid-template-rows: repeat(5, 1fr);

    .color-item {
        box-sizing: border-box;
        &:hover,
        &.on {
            border: solid 1px #000;
        }
    }
}

.with-angle {
    position: relative;
    overflow: hidden;
}

.angle {
    position: absolute;
    right: -4px;
    bottom: -4px;
    transform: rotate(45deg);
    width: 8px;
    height: 8px;
}
</style>

<script setup lang="ts">
import { DropdownButtonGroupExposed, FormItemProvided, InputPropsType, Rule_maxLength, Rule_minLength } from '@/types'
import { reactive, onMounted, ref, watch, inject } from 'vue'
import Quill from 'quill'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import AButton from './AButton.vue'
import AControlGroup from './AControlGroup.vue'
import ADropdownButtonGroup from './ADropdownButtonGroup.vue'
import ADropdown from './ADropdown.vue'
import { useFormItem } from '@/hooks/useFormItem'
import { useInput } from '@/hooks/useInput'
import { $t } from '@/lang'

interface PropsType extends InputPropsType<string | undefined, Rule_minLength | Rule_maxLength> {
    readonly?: boolean
    placeholder?: string
    autoFocus?: boolean
    uploadImageFunc?: (file: File) => string | Promise<string>
}
const props = withDefaults(defineProps<PropsType>(), {
    value: undefined,
    color: undefined,
    disabled: undefined,
    size: undefined,
    sharp: undefined,
    rules: () => [],
    readonly: false,
    placeholder: undefined,
    autoFocus: false,
    uploadImageFunc: (_file: File) => 'fakefile.ext',
})

interface StateType {
    currentFormat: any
    diff: any
    innerValue: string | undefined
}
const state = reactive<StateType>({
    currentFormat: {},
    diff: {},
    innerValue: undefined,
})

const emit = defineEmits<{
    (e: 'update:value', value?: string): void
    (e: 'validate', result: [boolean?, string?]): Promise<void>
}>()
const inputHook = useInput(props)
const formItemHook = useFormItem(props)
const formItem = inject<FormItemProvided | undefined>('formItem', undefined)

watch(
    () => props.value,
    () => {
        if (props.value !== state.innerValue) {
            editor!.setText(props.value ?? '')
        }
    },
)
watch(
    () => inputHook.validator.valid,
    () => {
        emit('validate', [inputHook.validator.valid, inputHook.validator.errorMessage])
    },
)
watch(
    () => state.diff,
    () => {
        methods.setDiffResult()
    },
)
onMounted(() => {
    editor = new Quill(editorRef.value!, {
        theme: 'snow',
        modules: {
            toolbar: false,
        },
        placeholder: props.placeholder,
        readOnly: props.readonly,
    })
    editor.on('editor-change', methods.onEditorChange)
    editor.on('text-change', methods.onTextChange)
})
const editorRef = ref<HTMLElement>()
let editor = undefined as Quill | undefined
const headerDropdownRef = ref<DropdownButtonGroupExposed>()
const sizeDropdownRef = ref<DropdownButtonGroupExposed>()
const colorDropdownRef = ref<DropdownButtonGroupExposed>()
const backgroundDropdownRef = ref<DropdownButtonGroupExposed>()
const imageInputRef = ref<HTMLInputElement>()
const alignDropdownRef = ref<DropdownButtonGroupExposed>()
const methods = {
    getEditorBodyEl() {
        return editorRef.value!.children[0]
    },
    onEditorChange() {
        let range = editor!.getSelection()
        if (range == null) {
            state.currentFormat = {}
            return
        }
        let formatObj = editor!.getFormat(range.index, range.length)
        state.currentFormat = formatObj ?? {}
    },
    onTextChange() {
        let content = methods.getEditorBodyEl().innerHTML
        state.innerValue = content
        emit('update:value', content)
        methods.computeDiff()
    },
    computeDiff() {
        let maxLengthRule = props.rules.find(a => a.name == 'maxLength')
        if (!maxLengthRule) {
            return null
        }
        let valueLength = methods.getValueToValidate().length
        state.diff = {
            valid: valueLength <= (maxLengthRule.value as number),
            text: `${valueLength}/${maxLengthRule.value}`,
        }
    },
    getValueToValidate() {
        return editor!.getText().replace(/\n$/, '')
    },
    formatHeader(value: number | false) {
        editor!.format('header', value)
        headerDropdownRef.value!.hidePop()
    },
    format(name: string) {
        let range = editor!.getSelection()
        if (range == null) {
            return
        }
        let formatObj = editor!.getFormat(range.index, range.length)
        if (formatObj[name] === true) {
            editor!.formatText(range.index, range.length, name, false)
        } else {
            editor!.formatText(range.index, range.length, name, true)
        }
    },
    formatIndent(value: string) {
        editor!.format('indent', value)
    },
    formatSize(value: string | false) {
        editor!.format('size', value)
        sizeDropdownRef.value!.hidePop()
    },
    formatColor(value: string | false) {
        editor!.format('color', value)
        colorDropdownRef.value!.hidePop()
    },
    formatBackground(value: string | false) {
        editor!.format('background', value)
        backgroundDropdownRef.value!.hidePop()
    },
    openImageSelector() {
        let range = editor!.getSelection()
        if (range == null) {
            return
        }
        imageInputRef.value!.click()
    },
    async inputImage() {
        let file = imageInputRef.value!.files![0]
        if (!file) {
            return
        }
        let src = await props.uploadImageFunc(file)
        let range = editor!.getSelection()
        editor!.insertEmbed(range?.index ?? 0, 'image', src)
        imageInputRef.value!.value = ''
    },
    showLinkInput() {
        let name = 'link'
        let range = editor!.getSelection()!
        let formatObj = editor!.getFormat(range.index, range.length)

        let defaultUrl = formatObj[name] as string

        let url = prompt('请输入链接', defaultUrl)
        if (url) {
            editor!.format(name, url)
        } else if (url === '') {
            editor!.format(name, false)
        } else if (url === null) {
            //prompt被取消，不做处理
        }
    },
    cancelLink() {
        editor!.format('link', false)
    },
    formatList(value: string) {
        let name = 'list'
        let range = editor!.getSelection()!
        let formatObj = editor!.getFormat(range.index, range.length)
        if (formatObj[name] === value) {
            editor!.format(name, false)
        } else {
            editor!.format(name, value)
        }
    },
    formatAlign(value: string | false) {
        editor!.format('align', value)
        alignDropdownRef.value!.hidePop()
    },
    setDiffResult() {
        let diff = state.diff
        formItem!.value.setDiffResult(diff?.valid, diff?.text)
    },
}
</script>
